package org.etnaframework.module.base.utils;

import javassist.*;
import javassist.bytecode.Descriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.TargetClassAware;
import org.springframework.beans.factory.Aware;
import org.springframework.lang.Nullable;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 提供Java反射相关的工具类
 *
 * @author jasyaf
 * @since 2023-12-08
 */
public class ReflectUtils {

    private static final Logger log = LoggerFactory.getLogger(ReflectUtils.class);

    private static final Map<Class<?>, List<Field>> allFieldsInSourceCodeOrder = new LinkedHashMap<>();

    private static final Map<Class<?>, List<Field>> declaredFieldsInSourceCodeOrder = new LinkedHashMap<>();

    private static final Map<Class<?>, List<Field>> fieldsInSourceCodeOrder = new LinkedHashMap<>();

    private static final Map<Class<?>, List<Method>> allMethodsInSourceCodeOrder = new LinkedHashMap<>();

    private static final Map<Class<?>, List<Method>> declaredMethodsInSourceCodeOrder = new LinkedHashMap<>();

    private static final Map<Class<?>, List<Method>> methodsInSourceCodeOrder = new LinkedHashMap<>();

    /**
     * 如果传入的是Spring代理类，返回其原始类，如果不是就直接返回传入的类
     */
    public static Class<?> getSpringProxyOriginClass(Class<?> clazz) {
        if (Aware.class.isAssignableFrom(clazz)) {
            return clazz.getSuperclass();
        }
        if (TargetClassAware.class.isAssignableFrom(clazz)) {
            return clazz.getSuperclass();
        }
        return clazz;
    }

    /**
     * 获取javassist对应的CtClass，如果获取失败返回null
     */
    public static CtClass getCtClass(Class<?> clazz) {
        try {
            Class<?> originClass = getSpringProxyOriginClass(clazz);
            ClassPool pool = ClassPool.getDefault();
            CtClass cc = pool.getOrNull(originClass.getName());
            if (null == cc) { // 按正常的流程来走，不应当会有获取不到的情况，但在某些容器中启动时由于是SystemClassLoader并不是应用的，导致加载不到这个class，所以需要手工添加一下
                pool.insertClassPath(new ClassClassPath(originClass));
                cc = pool.get(originClass.getName());
            }
            return cc;
        } catch (NotFoundException e) {
            log.error("Failed to get CtClass for {}", clazz.getName(), e);
        }
        return null;
    }

    /**
     * 按源码中的顺序（注意：java的反射机制无法保证返回的顺序）返回类中的所有符合筛选条件的字段
     * 包含所有非public的字段但不包括被继承的字段（返回内容和java反射的getDeclaredFields一样）
     *
     * @param clazz 需要被解析的类
     */
    public static List<Field> getDeclaredFieldsInSourceCodeOrder(Class<?> clazz) {
        List<Field> result = declaredFieldsInSourceCodeOrder.get(clazz);
        if (null == result) {
            synchronized (ReflectUtils.class) {
                result = declaredFieldsInSourceCodeOrder.get(clazz);
                if (null == result) {
                    Class<?> originClass = getSpringProxyOriginClass(clazz);
                    Map<String, Field> map = new LinkedHashMap<>();
                    for (Field f : originClass.getDeclaredFields()) {
                        map.put(f.getName(), f); // 建立字段名和字段的映射
                    }
                    Set<Field> temp = new LinkedHashSet<>(map.size());
                    try {
                        CtClass cc = getCtClass(originClass);
                        if (null != cc) {
                            for (CtField cf : cc.getDeclaredFields()) {
                                Field me = map.get(cf.getName());
                                if (null != me) {
                                    temp.add(me);
                                }
                            }
                        } else {
                            temp.addAll(map.values());
                        }
                    } catch (Throwable ex) { // 上面的流程都走过了，此处应当不可能抛此异常的，实在有问题，就用默认的方法列表
                        temp.addAll(map.values());
                    }
                    result = new ArrayList<>(temp);
                    declaredFieldsInSourceCodeOrder.put(clazz, result);
                }
            }
        }
        return result;
    }

    /**
     * 按源码中的顺序（注意：java的反射机制无法保证返回的顺序）返回类中的所有符合筛选条件的字段
     * 包含所有非public的字段但不包括被继承的字段（返回内容和java反射的getDeclaredFields一样）
     *
     * @param clazz  需要被解析的类
     * @param filter 如果需要进行筛选，可指定筛选条件
     */
    public static List<Field> getDeclaredFieldsInSourceCodeOrder(Class<?> clazz, FieldFilter filter) {
        List<Field> list = getDeclaredFieldsInSourceCodeOrder(clazz);
        if (null == filter) {
            return list;
        }
        return list.stream().filter(filter::matches).collect(Collectors.toList());
    }

    /**
     * 按源码中的顺序（注意：java的反射机制无法保证返回的顺序）返回类中的所有符合筛选条件的字段
     * 包含所有被继承的public字段（非public不会返回，返回内容和java反射的getFields一样）
     *
     * @param clazz 需要被解析的类
     */
    public static List<Field> getFieldsInSourceCodeOrder(Class<?> clazz) {
        List<Field> result = fieldsInSourceCodeOrder.get(clazz);
        if (result == null) {
            synchronized (ReflectUtils.class) {
                result = fieldsInSourceCodeOrder.get(clazz);
                if (result == null) {
                    Class<?> originClass = getSpringProxyOriginClass(clazz);
                    Map<String, Field> map = new LinkedHashMap<>();
                    for (Field f : originClass.getFields()) {
                        map.put(f.getName(), f); // 建立字段名和字段的映射
                    }
                    Set<Field> temp = new LinkedHashSet<>(map.size());
                    try {
                        CtClass cc = getCtClass(originClass);
                        if (null != cc) {
                            for (CtField cf : cc.getFields()) {
                                Field me = map.get(cf.getName());
                                if (null != me) {
                                    temp.add(me);
                                }
                            }
                        } else {
                            temp.addAll(map.values());
                        }
                    } catch (Throwable ex) { // 上面的流程都走过了，此处应当不可能抛此异常的，实在有问题，就用默认的方法列表
                        temp.addAll(map.values());
                    }
                    result = new ArrayList<>(temp);
                    fieldsInSourceCodeOrder.put(clazz, result);
                }
            }
        }
        return result;
    }

    /**
     * 按源码中的顺序（注意：java的反射机制无法保证返回的顺序）返回类中的所有符合筛选条件的字段
     * 包含所有被继承的public字段（非public不会返回，返回内容和java反射的getFields一样）
     *
     * @param clazz  需要被解析的类
     * @param filter 如果需要进行筛选，可指定筛选条件
     */
    public static List<Field> getFieldsInSourceCodeOrder(Class<?> clazz, FieldFilter filter) {
        List<Field> list = getFieldsInSourceCodeOrder(clazz);
        if (null == filter) {
            return list;
        }
        return list.stream().filter(filter::matches).collect(Collectors.toList());
    }

    /**
     * 按源码中的顺序（注意：java的反射机制无法保证返回的顺序）返回类中的所有符合筛选条件的字段
     * 包含本类所有的字段以及父类中的所有字段，父类字段优先显示
     *
     * @param clazz 需要被解析的类
     */
    public static List<Field> getAllFieldsInSourceCodeOrder(Class<?> clazz) {
        List<Field> result = allFieldsInSourceCodeOrder.get(clazz);
        if (result == null) {
            synchronized (ReflectUtils.class) {
                result = allFieldsInSourceCodeOrder.get(clazz);
                if (result == null) {
                    Class<?> originClass = getSpringProxyOriginClass(clazz);
                    Map<String, Field> map = new LinkedHashMap<>();
                    Class<?> targetClass = originClass;
                    do {
                        Field[] fields = targetClass.getDeclaredFields();
                        for (Field f : fields) {
                            map.put(f.getName(), f); // 建立字段名和字段的映射
                        }
                        targetClass = targetClass.getSuperclass();
                    } while (targetClass != null && targetClass != Object.class);
                    Set<Field> temp = new LinkedHashSet<>(map.size());
                    try {
                        CtClass cc = getCtClass(originClass);
                        if (null != cc) {
                            LinkedList<CtClass> list = new LinkedList<>();
                            CtClass targetCtClass = cc;
                            do {
                                list.addFirst(targetCtClass); // 把父类提到前面去处理，实现父类字段优先显示
                                targetCtClass = targetCtClass.getSuperclass();
                            } while (targetCtClass != null && !targetCtClass.getName().equals(Object.class.getName()));
                            for (CtClass c : list) {
                                CtField[] ctFields = c.getDeclaredFields();
                                for (CtField cf : ctFields) {
                                    Field me = map.get(cf.getName());
                                    if (null != me) {
                                        temp.add(me);
                                    }
                                }
                            }
                        } else {
                            temp.addAll(map.values());
                        }
                    } catch (Throwable ex) { // 上面的流程都走过了，此处应当不可能抛此异常的，实在有问题，就用默认的方法列表
                        temp.addAll(map.values());
                    }
                    result = new ArrayList<>(temp);
                    allFieldsInSourceCodeOrder.put(clazz, result);
                }
            }
        }
        return result;
    }

    /**
     * 按源码中的顺序（注意：java的反射机制无法保证返回的顺序）返回类中的所有符合筛选条件的字段
     * 包含本类所有的字段以及父类中的所有字段，父类字段优先显示
     *
     * @param clazz  需要被解析的类
     * @param filter 如果需要进行筛选，可指定筛选条件
     */
    public static List<Field> getAllFieldsInSourceCodeOrder(Class<?> clazz, FieldFilter filter) {
        List<Field> list = getAllFieldsInSourceCodeOrder(clazz);
        if (null == filter) {
            return list;
        }
        return list.stream().filter(filter::matches).collect(Collectors.toList());
    }

    /**
     * 返回类中的符合符合筛选条件的第1个字段，如果没有满足筛选条件的则会返回null，筛选范围包含本类所有的字段以及父类中的所有字段，父类字段优先
     *
     * @param clazz  需要被解析的类
     * @param filter 如果需要进行筛选，可指定筛选条件
     */
    public static Field getFirstField(Class<?> clazz, FieldFilter filter) {
        List<Field> result = getAllFieldsInSourceCodeOrder(clazz, filter);
        if (result.isEmpty()) {
            return null;
        }
        return result.get(0);
    }

    /**
     * 按源码中的顺序（注意：java的反射机制无法保证返回的顺序）返回类中的所有符合筛选条件的方法
     * 包含所有非public方法但不包括被继承的方法
     *
     * @param clazz 需要被解析的类
     */
    public static List<Method> getDeclaredMethodsInSourceCodeOrder(Class<?> clazz) {
        List<Method> result = declaredMethodsInSourceCodeOrder.get(clazz);
        if (result == null) {
            synchronized (ReflectUtils.class) {
                result = declaredMethodsInSourceCodeOrder.get(clazz);
                if (result == null) {
                    Class<?> originClass = getSpringProxyOriginClass(clazz);
                    Map<String, Method> map = new LinkedHashMap<>();
                    for (Method m : originClass.getDeclaredMethods()) {
                        // 建立方法名和方法的映射，改编自Method.toString()
                        StringBuilder sb = new StringBuilder();
                        sb.append(m.getName()).append('(');
                        Class<?>[] params = m.getParameterTypes();
                        for (int j = 0; j < params.length; j++) {
                            sb.append(params[j].getName());
                            if (j < (params.length - 1)) {
                                sb.append(',');
                            }
                        }
                        sb.append(')');
                        map.put(sb.toString(), m);
                    }
                    Set<Method> temp = new LinkedHashSet<>(map.size());
                    try {
                        CtClass cc = getCtClass(clazz);
                        if (null != cc) {
                            for (CtMethod cm : cc.getDeclaredMethods()) {
                                String key = cm.getName() + Descriptor.toString(cm.getSignature());
                                Method me = map.get(key);
                                if (null != me) {
                                    temp.add(me);
                                }
                            }
                        } else {
                            temp.addAll(map.values());
                        }
                    } catch (Throwable ex) { // 上面的流程都走过了，此处应当不可能抛此异常的，实在有问题，就用默认的方法列表
                        temp.addAll(map.values());
                    }
                    result = new ArrayList<>(temp);
                    declaredMethodsInSourceCodeOrder.put(clazz, result);
                }
            }
        }
        return result;
    }

    /**
     * 按源码中的顺序（注意：java的反射机制无法保证返回的顺序）返回类中的所有符合筛选条件的方法，包含所有非public方法但不包括被继承的方法
     *
     * @param clazz  需要被解析的类
     * @param filter 如果需要进行筛选，可指定筛选条件
     */
    public static List<Method> getDeclaredMethodsInSourceCodeOrder(Class<?> clazz, MethodFilter filter) {
        List<Method> list = getDeclaredMethodsInSourceCodeOrder(clazz);
        if (null == filter) {
            return list;
        }
        return list.stream().filter(filter::matches).collect(Collectors.toList());
    }

    /**
     * 按源码中的顺序（注意：java的反射机制无法保证返回的顺序）返回类中的所有符合筛选条件的方法，包含所有被继承的public方法（非public不会返回）
     *
     * @param clazz 需要被解析的类
     */
    public static List<Method> getMethodsInSourceCodeOrder(Class<?> clazz) {
        List<Method> result = methodsInSourceCodeOrder.get(clazz);
        if (result == null) {
            synchronized (ReflectUtils.class) {
                result = methodsInSourceCodeOrder.get(clazz);
                if (result == null) {
                    Class<?> originClass = getSpringProxyOriginClass(clazz);
                    Map<String, Method> map = new LinkedHashMap<>();
                    for (Method m : originClass.getMethods()) {
                        // 建立方法名和方法的映射，改编自Method.toString()
                        StringBuilder sb = new StringBuilder();
                        sb.append(m.getName()).append('(');
                        Class<?>[] params = m.getParameterTypes();
                        for (int j = 0; j < params.length; j++) {
                            sb.append(params[j].getName());
                            if (j < (params.length - 1)) {
                                sb.append(',');
                            }
                        }
                        sb.append(')');
                        map.put(sb.toString(), m);
                    }
                    Set<Method> temp = new LinkedHashSet<>(map.size());
                    try {
                        CtClass cc = getCtClass(originClass);
                        if (null != cc) {
                            for (CtMethod cm : cc.getMethods()) {
                                String key = cm.getName() + Descriptor.toString(cm.getSignature());
                                Method me = map.get(key);
                                if (null != me) {
                                    temp.add(me);
                                }
                            }
                        } else {
                            temp.addAll(map.values());
                        }
                    } catch (Throwable ex) { // 上面的流程都走过了，此处应当不可能抛此异常的，实在有问题，就用默认的方法列表
                        temp.addAll(map.values());
                    }
                    result = new ArrayList<>(temp);
                    methodsInSourceCodeOrder.put(clazz, result);
                }
            }
        }
        return result;
    }

    /**
     * 按源码中的顺序（注意：java的反射机制无法保证返回的顺序）返回类中的所有符合筛选条件的方法，包含所有被继承的public方法（非public不会返回）
     *
     * @param clazz  需要被解析的类
     * @param filter 如果需要进行筛选，可指定筛选条件
     */
    public static List<Method> getMethodsInSourceCodeOrder(Class<?> clazz, MethodFilter filter) {
        List<Method> list = getMethodsInSourceCodeOrder(clazz);
        if (null == filter) {
            return list;
        }
        return list.stream().filter(filter::matches).collect(Collectors.toList());
    }

    /**
     * 按源码中的顺序（注意：java的反射机制无法保证返回的顺序）返回类中的所有符合筛选条件的方法
     * 包含本类所有的方法以及父类/接口中的所有方法，显示优先级 接口>父类>子类
     *
     * @param clazz 需要被解析的类
     */
    public static List<Method> getAllMethodsInSourceCodeOrder(Class<?> clazz) {
        List<Method> result = allMethodsInSourceCodeOrder.get(clazz);
        if (null == result) {
            Class<?> originClass = getSpringProxyOriginClass(clazz);
            Set<Method> temp = new LinkedHashSet<>();
            // 优先处理父类和子类的方法
            if (originClass.getSuperclass() != null) {
                temp.addAll(getAllMethodsInSourceCodeOrder(originClass.getSuperclass()));
            } else if (originClass.isInterface()) {
                for (Class<?> superIfc : originClass.getInterfaces()) {
                    temp.addAll(getAllMethodsInSourceCodeOrder(superIfc));
                }
            }
            Map<String, Method> map = new LinkedHashMap<>();
            for (Method m : originClass.getDeclaredMethods()) {
                // 建立方法名和方法的映射，改编自Method.toString()
                StringBuilder sb = new StringBuilder();
                sb.append(m.getName()).append('(');
                Class<?>[] params = m.getParameterTypes();
                for (int j = 0; j < params.length; j++) {
                    sb.append(params[j].getName());
                    if (j < (params.length - 1)) {
                        sb.append(',');
                    }
                }
                sb.append(')');
                map.put(sb.toString(), m);
            }
            try {
                CtClass cc = getCtClass(originClass);
                if (null != cc) {
                    for (CtMethod cm : cc.getDeclaredMethods()) {
                        String key = cm.getName() + Descriptor.toString(cm.getSignature());
                        Method me = map.get(key);
                        if (null != me) {
                            temp.add(me);
                        }
                    }
                } else {
                    temp.addAll(map.values());
                }
            } catch (Throwable ex) { // 上面的流程都走过了，此处应当不可能抛此异常的，实在有问题，就用默认的方法列表
                temp.addAll(map.values());
            }
            result = new ArrayList<>(temp);
            allMethodsInSourceCodeOrder.put(clazz, result);
        }
        return result;
    }

    /**
     * 按源码中的顺序（注意：java的反射机制无法保证返回的顺序）返回类中的所有符合筛选条件的方法
     * 包含本类所有的方法以及父类/接口中的所有方法，显示父类和接口的显示优先级高于子类
     *
     * @param clazz  需要被解析的类
     * @param filter 如果需要进行筛选，可指定筛选条件
     */
    public static List<Method> getAllMethodsInSourceCodeOrder(Class<?> clazz, MethodFilter filter) {
        List<Method> list = getAllMethodsInSourceCodeOrder(clazz);
        if (null == filter) {
            return list;
        }
        return list.stream().filter(filter::matches).collect(Collectors.toList());
    }

    /**
     * 返回类中的符合符合筛选条件的第1个方法，如果没有满足筛选条件的则会返回null，筛选范围包含本类所有的方法以及父类中的所有方法，父类方法优先
     *
     * @param clazz  需要被解析的类
     * @param filter 如果需要进行筛选，可指定筛选条件
     */
    public static Method getFirstMethod(Class<?> clazz, MethodFilter filter) {
        List<Method> result = getAllMethodsInSourceCodeOrder(clazz, filter);
        if (result.isEmpty()) {
            return null;
        }
        return result.get(0);
    }

    /**
     * 根据字段获取对应的get方法，如果没找到返回null
     */
    public static Method getGetMethodByField(Class<?> clazz, Field field) {
        if (null == clazz || null == field) {
            return null;
        }
        String methodSuffix;
        String fieldName = field.getName();
        // 将字段的首字母转小写，如果第2个字母也是大写首字母就不转为小写了（跟Spring的beanName规则保持一致，这也是java的get/set方法名生成规则）
        if (fieldName.length() == 1) {
            methodSuffix = fieldName.toUpperCase(Locale.getDefault());
        } else if (fieldName.charAt(1) >= 'A' && fieldName.charAt(1) <= 'Z') {
            methodSuffix = fieldName;
        } else {
            methodSuffix = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
        }
        // IDE自动生成的get方法，如果是Boolean类型还是用的getXXX而不是isXXX，lombok的逻辑也是类似，所以下面只针对boolean做特殊处理
        String methodName = (boolean.class.equals(field.getType()) ? "is" : "get") + methodSuffix;
        return getFirstMethod(clazz, mt -> mt.getName().equals(methodName) && mt.getParameterTypes().length == 0 && mt.getReturnType().equals(field.getType()));
    }

    /**
     * 根据字段获取对应的set方法，如果没找到返回null
     */
    public static Method getSetMethodByField(Class<?> clazz, Field field) {
        if (null == clazz || null == field) {
            return null;
        }
        String methodSuffix;
        String fieldName = field.getName();
        // 将字段的首字母转小写，如果第2个字母也是大写首字母就不转为小写了（跟Spring的beanName规则保持一致，这也是java的get/set方法名生成规则）
        if (fieldName.length() == 1) {
            methodSuffix = fieldName.toUpperCase(Locale.getDefault());
        } else if (fieldName.charAt(1) >= 'A' && fieldName.charAt(1) <= 'Z') {
            methodSuffix = fieldName;
        } else {
            methodSuffix = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
        }
        String methodName = "set" + methodSuffix;
        return getFirstMethod(clazz, mt -> mt.getName().equals(methodName) && mt.getParameterTypes().length == 1 && mt.getParameterTypes()[0].equals(field.getType()));
    }

    /**
     * 生成一个新的实例，同时增强处理异常情况
     */
    public static <T> T newInstance(Class<T> clazz) {
        if (null == clazz) {
            throw new IllegalArgumentException("Class must not be null");
        }
        if (clazz.isInterface()) {
            throw new IllegalArgumentException("Class " + clazz.getName() + "is an interface");
        }
        if (Modifier.isAbstract(clazz.getModifiers())) {
            throw new IllegalArgumentException("Class " + clazz.getName() + "is an abstract class");
        }
        try {
            return clazz.newInstance();
        } catch (Throwable ex) {
            if (ex instanceof IllegalAccessException || ex.getCause() instanceof NoSuchMethodException) {
                throw new IllegalArgumentException("Please add a PUBLIC constructor without parameters for class " + clazz.getName() + ", otherwise the object instance cannot be created", ex);
            }
            throw new IllegalArgumentException(ex.getMessage(), ex);
        }
    }

    /**
     * 给字段设置值，内部已对异常做处理
     */
    public static void setFieldValue(Field field, @Nullable Object target, @Nullable Object value) {
        try {
            field.set(target, value);
        } catch (IllegalAccessException ex) {
            handleReflectionException(ex);
        }
    }

    /**
     * 获取字段值，内部已对异常做处理
     */
    @Nullable
    public static Object getFieldValue(Field field, @Nullable Object target) {
        try {
            return field.get(target);
        } catch (IllegalAccessException ex) {
            handleReflectionException(ex);
        }
        throw new IllegalStateException("Should never get here");
    }

    /**
     * 调用方法，内部已对异常做处理
     */
    public static Object invokeMethod(Method method, @Nullable Object target, @Nullable Object... args) {
        try {
            return method.invoke(target, args);
        } catch (Exception ex) {
            handleReflectionException(ex);
        }
        throw new IllegalStateException("Should never get here");
    }

    public static void handleReflectionException(Exception ex) {
        if (ex instanceof NoSuchMethodException) {
            throw new IllegalStateException("Method not found: " + ex.getMessage());
        }
        if (ex instanceof IllegalAccessException) {
            throw new IllegalStateException("Could not access method or field: " + ex.getMessage());
        }
        if (ex instanceof InvocationTargetException) {
            throw new UndeclaredThrowableException(((InvocationTargetException) ex).getTargetException());
        }
        if (ex instanceof RuntimeException) {
            throw (RuntimeException) ex;
        }
        throw new UndeclaredThrowableException(ex);
    }

    @FunctionalInterface
    public interface FieldFilter {

        boolean matches(Field field);
    }

    @FunctionalInterface
    public interface MethodFilter {

        boolean matches(Method method);
    }
}
